﻿using System;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;
using System.Text.RegularExpressions;
using System.DirectoryServices;
using System.Configuration;
using System.Collections.Generic;

namespace RioTinto.Web
{
    public class GALDirectory
    {
        public static GALPerson GetPersonByNetworkId(String networkId)
        {
            if (String.IsNullOrEmpty(networkId))
                return null;

            string[] idparts = networkId.TrimEnd('*').Split("\\".ToCharArray()[0]);
            string filter = string.Format("(&(sAMAccountName={0}))", idparts[idparts.Length - 1]);
            string domain = (idparts.Length > 1 ? idparts[0] + "." : string.Empty);

            List<GALPerson> results = SearchGAL(filter, domain, networkId);

            if (results.Count == 0)
                return null;

            return results[0];
        }

        public static GALPerson GetPersonByCN(String fullCN)
        {
            if (String.IsNullOrEmpty(fullCN))
                return null;

            string filter = string.Format("(&(cn={0}))", fullCN.TrimEnd('*'));

            List<GALPerson> results = SearchGAL(filter, string.Empty, fullCN.Substring(0, 3));

            switch (results.Count)
            {
                case 0:
                    return null;
                case 1:
                    return results[0];
                default:
                    GALPerson person = (from p in results
                                        where String.Compare(p.CName, fullCN.TrimEnd('*'), true) == 0
                                        select p).FirstOrDefault();

                    return person;
            }
        }

        // Add more operations here and mark them with [OperationContract]
        public static List<GALPerson> GetPersonCollection(string partialLastName)
        {
            if (String.IsNullOrEmpty(partialLastName))
                return new List<GALPerson>();

            String searchString = partialLastName.TrimEnd('*').Substring(0, 3);

            string filter = string.Format("(&(cn={0}*))", searchString);
            List<GALPerson> result = SearchGAL(filter, string.Empty, searchString);

            return (from p in result
                    where p.CName.StartsWith(partialLastName.TrimEnd('*'), StringComparison.OrdinalIgnoreCase)
                    select p).ToList();
        }

        private static Dictionary<String, List<GALPerson>> m_GALCache = new Dictionary<string, List<GALPerson>>();
        private static Object m_SyncLock = new Object();

        private static List<GALPerson> SearchGAL(string filter, string domain, string cacheKey)
        {
            // check cache first
            cacheKey = cacheKey.ToUpper();
            if (m_GALCache.ContainsKey(cacheKey))
            {
                return m_GALCache[cacheKey];
            }

            using (DirectoryEntry directoryEntry = new DirectoryEntry(
                string.Format(ConfigurationManager.AppSettings["GlobalActiveDirectoryRoot"], domain),
                ConfigurationManager.AppSettings["ServiceAccountUsername"],
                ConfigurationManager.AppSettings["ServiceAccountPassword"],
                AuthenticationTypes.Secure)) //string.Format(ConfigurationManager.AppSettings["GlobalActiveDirectoryRoot"], domain),
            {
                using (DirectorySearcher directorySearcher = new DirectorySearcher(directoryEntry))
                {
                    directorySearcher.Filter = filter;
                    directorySearcher.SearchScope = SearchScope.Subtree;
                    directorySearcher.CacheResults = false; // we want fresh data

                    ////for performance, make sure we only load the properties we're interested in
                    directorySearcher.PropertiesToLoad.Clear();
                    directorySearcher.PropertiesToLoad.Add("sAMAccountName");
                    directorySearcher.PropertiesToLoad.Add("CN");
                    directorySearcher.PropertiesToLoad.Add("givenname");
                    directorySearcher.PropertiesToLoad.Add("sn");
                    directorySearcher.PropertiesToLoad.Add("mail");
                    directorySearcher.PropertiesToLoad.Add("DC");

                    List<GALPerson> persons = new List<GALPerson>();
                    using (SearchResultCollection results = directorySearcher.FindAll())
                    {
                        if (results.Count > 0)
                        {
                            foreach (SearchResult result in results)
                            {
                                try
                                {
                                    //if we don't have both properties the account won't work with this application
                                    if (result.Properties["sAMAccountName"].Count == 0 || result.Properties["CN"].Count == 0
                                        || result.Properties["mail"].Count == 0)
                                        continue;

                                    AddGALPerson(persons, result);
                                }
                                catch (Exception ex)
                                {
                                    //TODO: do we need to do anything here? maybe report it?
                                    Console.WriteLine(ex.Message + "\r\n" + ex.StackTrace);
                                }
                            }
                        }
                    }

                    lock (m_SyncLock)
                    {
                        if (!m_GALCache.ContainsKey(cacheKey))
                        {
                            m_GALCache.Add(cacheKey, persons);
                        }
                    }

                    return persons;
                }
            }
        }

        private static void AddGALPerson(List<GALPerson> persons, SearchResult result)
        {

            string gpFirstAndMiddleName = string.Empty, gpLastName = string.Empty,
                gpDomain = string.Empty, gpNetworkId = string.Empty, gpCName = string.Empty;

            //get the network id
            //GC://riotinto.org/CN=Miller\, Mark W. (RTEA),OU=People,OU=SpringCreek,OU=KEC,OU=RTSI,DC=ae,DC=riotinto,DC=org
            String[] gpDomainParts = result.Path.Substring(result.Path.IndexOf(",DC=")).Replace(",DC=", ",").Split(',');
            for (int i = 0; i < gpDomainParts.Length; i++)
            {
                if (String.Compare(gpDomainParts[i], "riotinto", true) != 0
                    && String.Compare(gpDomainParts[i], "org", true) != 0)
                {
                    gpDomain = String.Format("{0}\\", gpDomainParts[i]);
                }
            }

            gpNetworkId = String.Format("{0}{1}", gpDomain, result.Properties["sAMAccountName"][0]);

            //get the names
            gpFirstAndMiddleName = (string)result.Properties["givenname"][0];
            gpLastName = (string)result.Properties["sn"][0];

            //get the CN
            gpCName = (string)result.Properties["CN"][0];

            //add this user to the list
            if (string.IsNullOrEmpty(gpNetworkId) == false && string.IsNullOrEmpty(gpLastName) == false)
            {
                persons.Add(new GALPerson(gpFirstAndMiddleName, string.Empty, gpLastName, gpNetworkId, gpCName));
            }
        }

    }

    /// <summary>
    /// This class gives us only what we need from the weightier Person class 
    /// that comes from the web service - we don't need all of that data.
    /// </summary>
    public class GALPerson : IComparable
    {
        public string FirstName
        {
            get;
            private set;
        }

        public string MiddleInitial
        {
            get;
            private set;
        }

        public string LastName
        {
            get;
            private set;
        }

        public string NetworkId
        {
            get;
            private set;
        }

        public string CName
        {
            get;
            private set;
        }

        private GALPerson() { }

        public GALPerson(string firstName, string middleInitial, string lastName, string networkId, string cName)
        {
            FirstName = firstName;
            MiddleInitial = middleInitial;
            LastName = lastName;
            NetworkId = networkId;
            CName = cName;
        }

        #region IComparable Members

        public int CompareTo(object obj)
        {
            return LastName.CompareTo(((GALPerson)obj).LastName);
        }

        #endregion
    }

}
